package com.mindata.blockchain.core.service;

import cn.hutool.core.collection.CollectionUtil;
import com.mindata.blockchain.block.Block;
import com.mindata.blockchain.block.BlockBody;
import com.mindata.blockchain.block.BlockHeader;
import com.mindata.blockchain.block.Instruction;
import com.mindata.blockchain.block.merkle.MerkleTree;
import com.mindata.blockchain.common.BlockUtils;
import com.mindata.blockchain.common.CommonUtil;
import com.mindata.blockchain.common.ResultPack;
import com.mindata.blockchain.common.Sha256;
import com.mindata.blockchain.common.SystemConfig;
import com.mindata.blockchain.common.exception.BlockException;
import com.mindata.blockchain.common.exception.TrustSDKException;
import com.mindata.blockchain.core.manager.DbBlockManager;
import com.mindata.blockchain.core.manager.PermissionManager;
import com.mindata.blockchain.socket.body.RpcBlockBody;
import com.mindata.blockchain.socket.client.PacketSender;
import com.mindata.blockchain.socket.common.Const;
import com.mindata.blockchain.socket.packet.BlockPacket;
import com.mindata.blockchain.socket.packet.PacketBuilder;
import com.mindata.blockchain.socket.packet.PacketType;
import com.mindata.blockchain.socket.raft.client.RaftService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author wuweifeng wrote on 2018/3/8.
 */
@Service
@Slf4j
public class BlockService {
    @Resource
    private InstructionService instructionService;
    private int version = Const.VERSION;
    @Resource
    private PacketSender packetSender;
    @Resource
    private DbBlockManager dbBlockManager;
    @Resource
    private PermissionManager permissionManager;
    @Resource
    private SystemConfig systemConfig;
    @Resource
    private RaftService raftService;

    /**
     * 校验指令集是否合法
     *
     * @param blockBody
     * @return 是否合法，为null则校验通过，其他则失败并返回原因
     * @throws TrustSDKException
     */
    public ResultPack check(BlockBody blockBody) throws TrustSDKException {
        if (blockBody == null) {
            return ResultPack.failed("请求参数缺失");
        }

        List<Instruction> instructions = blockBody.getInstructions();
        if (CollectionUtil.isEmpty(instructions)) {
            return ResultPack.failed("指令信息不能为空");
        }

        for (Instruction instruction : instructions) {
            if (!instructionService.checkSign(instruction)) {
                return ResultPack.failed("签名校验不通过");
            }
            if (!instructionService.checkHash(instruction)) {
                return ResultPack.failed("Hash校验不通过");
            }
        }

        if (!permissionManager.checkPermission(instructions)) {
            return ResultPack.failed("权限校验不通过");
        }

        return ResultPack.succeed();
    }

    /**
     * 添加新的区块
     *
     * @param blockBody
     * @return Block
     */
    public Block addBlock(BlockBody blockBody) {
        //打包区块账户
        String coinBasePubKey = dbBlockManager.getCoinBasePubKey();
        if (StringUtils.isBlank(coinBasePubKey)) {
            throw new BlockException("节点无法生成区块，先创建账户");
        }

        List<Instruction> instructions = blockBody.getInstructions();
        List<String> hashList = instructions.stream().map(Instruction::getHash).collect(Collectors.toList());
        Block lastBlock = dbBlockManager.getLastBlock();
        long blockNumber = 1;
        String lastBlockHash = null;
        if (lastBlock != null) {
            blockNumber = lastBlock.getBlockHeader().getNumber() + 1;
            lastBlockHash = lastBlock.getHash();
        }

        //区块头构建
        BlockHeader blockHeader = new BlockHeader();
        blockHeader.setHashList(hashList);
        //计算所有指令的hashRoot
        blockHeader.setHashMerkleRoot(new MerkleTree(hashList).build().getRoot());
        blockHeader.setPublicKey(coinBasePubKey);
        blockHeader.setTimeStamp(CommonUtil.getNow());
        blockHeader.setVersion(version);
        blockHeader.setNumber(blockNumber);
        blockHeader.setHashPreviousBlock(lastBlockHash);

        //区块构建
        Block block = new Block();
        block.setBlockBody(blockBody);
        block.setBlockHeader(blockHeader);

        block.setHash(Sha256.sha256(BlockUtils.getBlockSignStr(block)));

        switch (systemConfig.getConsensusType()) {
            case PBFT:
                BlockPacket blockPacket = new PacketBuilder<>().setType(PacketType.GENERATE_BLOCK_REQUEST.getKey()).setBody(new
                        RpcBlockBody(block)).build();
                //广播给其他人做验证
                packetSender.sendGroup(blockPacket);
                break;
            case RAFT:
                try {
                    raftService.set(block.getHash(), block);
                } catch (Exception e) {
                    log.error("raft add block error", e);
                }
                break;
            default:
                break;
        }
        return block;
    }

}
